﻿using System;
using System.Threading;

namespace LogikBug.Async
{
	/// <summary>
	/// Provides a result for an asynchronous operation.
	/// </summary>
	public class AsyncResult : DisposableObject, IAsyncResult
	{
		#region Fields

		private const int _statePending = 0;
		private const int _stateSynchronously = 1;
		private const int _stateAsynchronously = 2;

		private readonly object _syncRoot = new object();

		private readonly object _asyncState;
		private readonly AsyncCallback _callback;

		private object _key;
		private Exception _exception;
		private ManualResetEvent _waitHandle;

		private int _completedState = _statePending;

		#endregion

		#region Constructors

		public AsyncResult(AsyncCallback callback, object state) : this(callback, state, false)
		{
		}

		public AsyncResult(AsyncCallback callback, object state, bool completed)
		{
			_callback = callback;
			_asyncState = state;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the SyncRoot for this current asynchronous operation.
		/// </summary>
		protected object SyncRoot
		{
			get
			{
				return _syncRoot;
			}
		}

		/// <summary>
		/// Gets the <see cref="ManualResetEvent"/> for this current asynchronous operation.
		/// </summary>
		protected ManualResetEvent ResetEvent
		{
			get
			{
				lock (_syncRoot)
				{
					if (_waitHandle == null)
					{
						bool isCompleted = Completed;
						var resetEvent = new ManualResetEvent(isCompleted);

						if (Interlocked.CompareExchange(ref _waitHandle, resetEvent, null) != null)
						{
							resetEvent.Close();
						}
						else
						{
							if (!isCompleted && IsCompleted)
							{
								_waitHandle.Set();
							}
						}
					}

					return _waitHandle;
				}
			}
		}

		/// <summary>
		/// Gets a value that indicates whether the asynchronous operation has completed.
		/// </summary>
		protected bool Completed
		{
			get
			{
				lock (_syncRoot)
				{
					return Thread.VolatileRead(ref _completedState) != _statePending;
				}
			}
		}

		/// <summary>
		/// Gets the <see cref="AsyncCallback"/> that is invoked when this asynchronous operation is completed.
		/// </summary>
		protected AsyncCallback AsyncCallback
		{
			get
			{
				lock (_syncRoot)
				{
					return _callback;
				}
			}
		}

		/// <summary>
		/// Gets the key associated with this current asynchronous operation.
		/// </summary>
		public object Key
		{
			get
			{
				lock (_syncRoot)
				{
					return _key;
				}
			}
			set
			{
				lock (_syncRoot)
				{
					_key = value;
				}
			}
		}

		/// <summary>
		/// Gets the <see cref="Exception"/> if any that occurred while the current asynchronous operation was executing.
		/// </summary>
		public Exception Exception
		{
			get
			{
				lock (_syncRoot)
				{
					return _exception;
				}
			}
		}

		#region IAsyncResult Members

		/// <summary>
		/// Gets a user-defined object that qualifies or contains information about an asynchronous operation.
		/// </summary>
		public object AsyncState
		{
			get
			{
				lock (_syncRoot)
				{
					return _asyncState;
				}
			}
		}

		/// <summary>
		/// Gets a <see cref="WaitHandle"/> that is used to wait for an asynchronous operation to complete.
		/// </summary>
		public WaitHandle AsyncWaitHandle
		{
			get
			{
				return ResetEvent;
			}
		}

		/// <summary>
		/// Gets a value that indicates whether the asynchronous operation completed synchronously.
		/// </summary>
		public bool CompletedSynchronously
		{
			get
			{
				lock (_syncRoot)
				{
					return Thread.VolatileRead(ref _completedState) == _stateSynchronously;
				}
			}
		}

		/// <summary>
		/// Gets a value that indicates whether the asynchronous operation has completed.
		/// </summary>
		public bool IsCompleted
		{
			get
			{
				return Completed;
			}
		}

		#endregion

		#endregion

		#region Methods

		private void InvokeCallback()
		{
			if (AsyncCallback != null)
			{
				AsyncCallback(this);
			}
		}

		protected void SignalCompletion()
		{
			InvokeCallback();
		}

		public void Complete(bool completedSynchronously)
		{
			lock (_syncRoot)
			{
				var completedState = completedSynchronously ? _stateSynchronously : _stateAsynchronously;

				if (Interlocked.Exchange(ref _completedState, completedState) != _statePending)
				{
					throw new InvalidOperationException("You can set a result only once");
				}

				if (_waitHandle != null) _waitHandle.Set();
			}

			SignalCompletion();
		}

		public void Complete(Exception exception, bool completedSynchronously)
		{
			lock (_syncRoot)
			{
				_exception = exception;
			}

			Complete(completedSynchronously);
		}

		public object EndInvoke()
		{
			return EndInvoke(false);
		}

		public object EndInvoke(bool throwIfErrored)
		{
			if (!IsCompleted)
			{
				AsyncWaitHandle.WaitOne();
				AsyncWaitHandle.Close();
				_waitHandle = null;
			}

			if (_exception != null && throwIfErrored)
			{
				throw _exception;
			}

			return _asyncState;
		}

		#region DisposableObject Members

		public override void Dispose()
		{
			lock (_syncRoot)
			{
				base.Dispose();
			}
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing && _waitHandle != null)
			{
				_waitHandle.Dispose();
			}
		}

		#endregion

		#endregion
	}

	/// <summary>
	/// Provides a strongly typed result for an asynchronous operation.
	/// </summary>
	/// <typeparam name="T">The type of result.</typeparam>
	public class AsyncResult<T> : AsyncResult, IAsyncResult<T>, IAsyncResult
	{
		#region Fields

		private T _result;

		#endregion

		#region Constructors

		public AsyncResult(AsyncCallback callback, object state) : base(callback, state, false)
		{
		}

		public AsyncResult(AsyncCallback callback, object state, bool completed) : base(callback, state, completed)
		{
		}

		#endregion

		#region Properties

		public T Result
		{
			get
			{
				lock (SyncRoot)
				{
					return _result;
				}
			}
		}

		#endregion

		#region Methods

		public void Complete(T result, bool completedSynchronously)
		{
			lock (SyncRoot)
			{
				_result = result;
			}

			Complete(completedSynchronously);
		}

		T IAsyncResult<T>.EndInvoke()
		{
			EndInvoke();
			return Result;
		}

		#endregion
	}
}
